在前面的篇章中,我們學會了如何用型別系統確保記憶體安全。
今天我們要探討另一個關鍵問題:如何設計模組邊界,讓錯誤的使用方式變得不可能。
把握一個關鍵:公開的 API 越少,維護的負擔越小,使用者犯錯的機會也越少。
// 預設所有東西都是私有的
mod database {
struct Connection { // 私有結構體
host: String, // 私有欄位
}
impl Connection {
fn connect() -> Self { // 私有方法
Connection {
host: "localhost".to_string(),
}
}
}
}
// 這些都無法從外部存取
// let conn = database::Connection::connect(); // ❌ 編譯錯誤
對比其他語言:
# Python:所有東西預設都是公開的
class Connection:
def __init__(self):
self._host = "localhost" # 慣例上的私有,但仍可存取
# 可以存取「私有」屬性
conn = Connection()
print(conn._host) # 可以執行,只是不建議
// Go:大寫開頭是公開的,小寫是私有的
type connection struct { // 私有
host string // 私有
}
type Connection struct { // 公開
Host string // 公開
}
mod outer {
// pub:完全公開
pub fn public_function() {}
// pub(crate):在當前 crate 內公開
pub(crate) fn crate_function() {}
// pub(super):在父模組中公開
pub(super) fn parent_function() {}
// pub(in path):在指定路徑中公開
pub(in crate::outer) fn limited_function() {}
// 預設:私有
fn private_function() {}
mod inner {
pub fn can_access_parent() {
super::parent_function(); // ✅ 可以
// super::private_function(); // ❌ 不行
}
}
}
// 糟糕的設計:公開所有欄位
pub struct BankAccount {
pub balance: i64, // 任何人都可以修改
}
fn bad_usage() {
let mut account = BankAccount { balance: 1000 };
// 直接修改餘額,繞過所有檢查
account.balance = -500; // 負數餘額!
account.balance = i64::MAX; // 無限金錢!
}
// 好的設計:封裝內部狀態
pub struct BankAccount {
balance: i64, // 私有欄位
}
impl BankAccount {
// 建構函式確保初始狀態有效
pub fn new(initial_balance: i64) -> Result<Self, String> {
if initial_balance < 0 {
return Err("初始餘額不能為負數".to_string());
}
Ok(BankAccount { balance: initial_balance })
}
// 只提供安全的操作
pub fn deposit(&mut self, amount: i64) -> Result<(), String> {
if amount <= 0 {
return Err("存款金額必須為正數".to_string());
}
self.balance = self.balance.checked_add(amount)
.ok_or("餘額溢位".to_string())?;
Ok(())
}
pub fn withdraw(&mut self, amount: i64) -> Result<(), String> {
if amount <= 0 {
return Err("提款金額必須為正數".to_string());
}
if amount > self.balance {
return Err("餘額不足".to_string());
}
self.balance -= amount;
Ok(())
}
// 只讀存取
pub fn balance(&self) -> i64 {
self.balance
}
}
// 現在無法繞過檢查
fn safe_usage() {
let mut account = BankAccount::new(1000).unwrap();
// 所有操作都經過驗證
account.deposit(500).unwrap();
account.withdraw(200).unwrap();
// 無法直接修改餘額
// account.balance = -500; // ❌ 編譯錯誤
}
關鍵洞察:透過封裝,我們把「不變式」(invariant) 從文件變成了編譯器檢查。
// 糟糕:容易混淆
fn transfer(from: u64, to: u64, amount: u64) {
// from 和 to 是帳號 ID?還是金額?
// 編譯器無法幫我們檢查
}
// 容易犯錯
transfer(100, 50, 12345); // 參數順序錯了!
// 用 newtype 建立語義明確的型別
pub struct AccountId(u64);
pub struct Amount(u64);
impl AccountId {
pub fn new(id: u64) -> Self {
AccountId(id)
}
}
impl Amount {
pub fn new(value: u64) -> Result<Self, String> {
if value == 0 {
return Err("金額不能為零".to_string());
}
Ok(Amount(value))
}
pub fn value(&self) -> u64 {
self.0
}
}
// 現在函式簽名清晰明確
fn transfer(from: AccountId, to: AccountId, amount: Amount) {
// 型別系統確保參數不會混淆
}
// 使用時更安全
fn safe_transfer() {
let from = AccountId::new(12345);
let to = AccountId::new(67890);
let amount = Amount::new(100).unwrap();
transfer(from, to, amount);
// 無法傳錯參數
// transfer(amount, from, to); // ❌ 型別不符
}
use std::fmt;
// 封裝敏感資訊
pub struct Password(String);
impl Password {
pub fn new(password: String) -> Result<Self, String> {
if password.len() < 8 {
return Err("密碼長度至少 8 個字元".to_string());
}
Ok(Password(password))
}
// 提供安全的驗證方法
pub fn verify(&self, input: &str) -> bool {
self.0 == input
}
}
// 防止密碼被意外印出
impl fmt::Debug for Password {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Password(***)")
}
}
impl fmt::Display for Password {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "***")
}
}
// 使用時更安全
fn handle_password() {
let password = Password::new("secret123".to_string()).unwrap();
// 無法直接存取內部字串
// let s = password.0; // ❌ 私有欄位
// 印出時不會洩漏
println!("{:?}", password); // Password(***)
}
// src/lib.rs
mod database;
mod api;
mod models;
pub use database::Connection;
pub use api::Router;
pub use models::User;
// src/database.rs
pub struct Connection {
// 實作細節
}
impl Connection {
pub fn new() -> Self {
Connection {}
}
}
// src/api.rs
pub struct Router {
// 實作細節
}
// src/models.rs
pub struct User {
pub id: u64,
pub name: String,
}
// 清晰的分層結構
mod infrastructure {
pub(crate) mod database {
pub(crate) struct Connection {
// 只在 infrastructure 層可見
}
}
pub(crate) mod cache {
pub(crate) struct Cache {
// 只在 infrastructure 層可見
}
}
}
mod domain {
pub struct User {
id: u64,
name: String,
}
impl User {
pub fn new(id: u64, name: String) -> Self {
User { id, name }
}
}
}
mod application {
use crate::domain::User;
use crate::infrastructure::database::Connection;
pub struct UserService {
conn: Connection,
}
impl UserService {
pub fn create_user(&self, name: String) -> User {
// 業務邏輯
User::new(1, name)
}
}
}
// 只公開最上層的 API
pub use application::UserService;
pub use domain::User;
// 糟糕:unsafe 暴露給使用者
pub fn bad_api() -> *mut u8 {
unsafe {
libc::malloc(1024) as *mut u8
}
}
// 使用者必須處理 unsafe
fn user_code() {
let ptr = bad_api();
unsafe {
*ptr = 42; // 使用者也要寫 unsafe
}
}
// 好的設計:unsafe 被封裝在內部
pub struct Buffer {
ptr: *mut u8,
len: usize,
}
impl Buffer {
pub fn new(size: usize) -> Self {
unsafe {
let ptr = libc::malloc(size) as *mut u8;
if ptr.is_null() {
panic!("記憶體分配失敗");
}
Buffer { ptr, len: size }
}
}
// 提供安全的 API
pub fn write(&mut self, index: usize, value: u8) -> Result<(), String> {
if index >= self.len {
return Err("索引超出範圍".to_string());
}
unsafe {
*self.ptr.add(index) = value;
}
Ok(())
}
pub fn read(&self, index: usize) -> Result<u8, String> {
if index >= self.len {
return Err("索引超出範圍".to_string());
}
unsafe {
Ok(*self.ptr.add(index))
}
}
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
libc::free(self.ptr as *mut _);
}
}
}
// 使用者不需要寫 unsafe
fn safe_user_code() {
let mut buffer = Buffer::new(1024);
buffer.write(0, 42).unwrap();
let value = buffer.read(0).unwrap();
}
use std::sync::{Arc, Mutex};
// 糟糕:暴露鎖的細節
pub struct BadCounter {
pub value: Arc<Mutex<i32>>, // 使用者需要知道鎖的存在
}
// 使用者必須處理鎖
fn bad_usage() {
let counter = BadCounter {
value: Arc::new(Mutex::new(0)),
};
let mut guard = counter.value.lock().unwrap();
*guard += 1;
// 忘記釋放鎖?死鎖!
}
// 好的設計:封裝鎖的細節
pub struct Counter {
value: Arc<Mutex<i32>>, // 私有
}
impl Counter {
pub fn new() -> Self {
Counter {
value: Arc::new(Mutex::new(0)),
}
}
pub fn increment(&self) {
let mut guard = self.value.lock().unwrap();
*guard += 1;
// guard 自動釋放
}
pub fn get(&self) -> i32 {
let guard = self.value.lock().unwrap();
*guard
}
}
impl Clone for Counter {
fn clone(&self) -> Self {
Counter {
value: Arc::clone(&self.value),
}
}
}
// 使用者不需要知道鎖的存在
fn good_usage() {
let counter = Counter::new();
counter.increment();
println!("值: {}", counter.get());
}
// 糟糕:參數太多,容易出錯
pub struct Server {
host: String,
port: u16,
timeout: u64,
max_connections: usize,
enable_tls: bool,
}
impl Server {
pub fn new(
host: String,
port: u16,
timeout: u64,
max_connections: usize,
enable_tls: bool,
) -> Self {
Server { host, port, timeout, max_connections, enable_tls }
}
}
// 使用時容易搞混參數
fn bad_usage() {
let server = Server::new(
"localhost".to_string(),
8080,
30,
100,
true,
);
}
// 好的設計:Builder 模式
pub struct Server {
host: String,
port: u16,
timeout: u64,
max_connections: usize,
enable_tls: bool,
}
pub struct ServerBuilder {
host: String,
port: u16,
timeout: u64,
max_connections: usize,
enable_tls: bool,
}
impl ServerBuilder {
pub fn new() -> Self {
ServerBuilder {
host: "localhost".to_string(),
port: 8080,
timeout: 30,
max_connections: 100,
enable_tls: false,
}
}
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn timeout(mut self, timeout: u64) -> Self {
self.timeout = timeout;
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.max_connections = max;
self
}
pub fn enable_tls(mut self, enable: bool) -> Self {
self.enable_tls = enable;
self
}
pub fn build(self) -> Result<Server, String> {
// 驗證配置
if self.port == 0 {
return Err("埠號不能為 0".to_string());
}
Ok(Server {
host: self.host,
port: self.port,
timeout: self.timeout,
max_connections: self.max_connections,
enable_tls: self.enable_tls,
})
}
}
// 使用時清晰明確
fn good_usage() {
let server = ServerBuilder::new()
.host("0.0.0.0")
.port(8080)
.timeout(60)
.enable_tls(true)
.build()
.unwrap();
}
// 只公開必要的東西
pub struct Public {
private_field: i32, // 預設私有
}
impl Public {
pub fn public_method(&self) {} // 明確公開
fn private_method(&self) {} // 預設私有
}
// 讓錯誤的使用方式變得不可能
pub struct ValidatedEmail(String);
impl ValidatedEmail {
pub fn new(email: String) -> Result<Self, String> {
if !email.contains('@') {
return Err("無效的電子郵件".to_string());
}
Ok(ValidatedEmail(email))
}
}
// unsafe 和鎖應該被封裝在內部
pub struct SafeWrapper {
inner: UnsafeInner, // 私有
}
impl SafeWrapper {
pub fn safe_operation(&self) {
// 內部處理 unsafe
}
}
// 只公開使用者真正需要的東西
mod internal {
pub(crate) fn helper() {} // 只在 crate 內可見
}
pub fn public_api() {
internal::helper(); // 內部使用
}
:好的 API 設計不是提供更多功能,而是讓使用者只能用正確的方式使用。
在下一篇中,我們將探討 進階智慧指標,看看 Pin
和特徵物件如何處理更複雜的所有權場景。